這題要在一個 m x n 的棋盤上,找到被 'X' 圍住的區,並把區域裡的 'O' 轉成 'X',重點在區域的邊界條件,就是所有不能被包圍的 'O' 一定在邊界或跟邊界相連,所以,主要的問題就是怎麼有效的找到這些不該被改成 'X' 的 'O'。
想法:
拆問題,目標是改那些被 'X' 完全包住的 'O',但不能改跟邊界連在一起的 'O',因為這些 'O' 沒辦法被包圍所以可以從邊界開始,找跟邊界相連的 'O',把它們標成不能改。
深度優先搜尋 (DFS) 或廣度優先搜尋 (BFS),從邊界開始,對每個 'O' 搜索,把所有跟邊界相連的 'O' 標成另外的符號(例如 '#'),這樣就不會誤修改,雙重遍歷,遍歷整個棋盤,把所有剩下的 'O'(沒被標記的 'O')改成 'X',因為這些 'O' 是被包起來的,再把標記的 '#' 恢復成 'O',邊界情況,要注意處理棋盤大小是 1x1 或非常長的棋盤,這些特例可能出現錯誤結果,所以一定要設好邊界條件。
步驟:
從棋盤的四條邊出發,找出所有跟邊界相連的 'O',用 DFS 或 BFS 遍歷這些邊界的 'O' 然後標記,遍歷整個棋盤,把那些沒標記的 'O' 改成 'X',然後把標過的 'O' 恢復成 'O'。
心得:
這題主要考圖的遍歷技術跟對邊界情況的考慮,尤其是深度優先或廣度優先的應用,當棋盤增大,這種搜索方法依然能高效運行,這讓我更理解 DFS 和 BFS 的用途,對邊界條件和特殊情況的處理技巧也有進步(我自己覺得啦),對之後的圖論問題處理比較有自信。
程式碼:
class Solution(object):
def solve(self, board):
"""
:type board: List[List[str]]
:rtype: None Do not return anything, modify board in-place instead.
"""
if not board or not board[0]:
return
#取得行數、列數
rows, cols = len(board), len(board[0])
#定義一個深度優先搜尋函數
def dfs(r, c):
# 如果當前點超出邊界或不為是'O',直接返回
if r < 0 or r >= rows or c < 0 or c >= cols or board[r][c] != 'O':
return
#標記當前的 'O',暫時用 '#' 表不被修改
board[r][c] = '#'
#遞迴搜尋四個方向
dfs(r + 1, c) # 向下
dfs(r - 1, c) # 向上
dfs(r, c + 1) # 向右
dfs(r, c - 1) # 向左
#從邊界的 'O' 開始深度優先搜尋,標記跟邊界相連的 'O'
for i in range(rows):
dfs(i, 0) # 第一列
dfs(i, cols - 1) # 最後一列
for j in range(cols):
dfs(0, j) # 第一行
dfs(rows - 1, j) # 最後一行
#遍歷整個棋盤,把所有沒標記的 'O' 變成 'X'
#同時把標記成 '#' 的恢復 'O'
for i in range(rows):
for j in range(cols):
if board[i][j] == 'O':
board[i][j] = 'X' # 被包圍的 'O' 改 'X'
elif board[i][j] == '#':
board[i][j] = 'O' # 邊界相連的 'O' 恢復
思路:
找出所有跟邊界相連的 'O',從邊界開始,對每個 'O' 深度優先搜索(DFS),把這些 'O' 標記成 #,表它們不該被轉換,遍歷棋盤修改,把所有沒標的 'O' 轉成 'X',因為這些 'O' 是被包圍的,然後把標記 # 的恢復成 'O'。
解釋:
DFS 遍歷,深度優先搜尋是解決這類「連通性」問題的重要技巧,這種問題通常會用到 DFS 或 BFS 來遍歷某個區域,處理邊界,因為任何跟邊界相連的 'O' 都不能被包圍,所以先從邊界出發用DFS,通過圖的遍歷解決「區域包圍」問題,且考慮邊界的處理,增強了圖論問題的實做能力。